<?php
/* user class that handles authentication, identifcation 
	and other user related methods. */
class user{
	/* cookie's name where we store user auth data (encrypted) */
	const AUTH_COOKIE_NAME = "knac";

	// local auth lib instance
	private $authLib;

	// local db handle
	private $db;

	// defines whether user logged in or not
	private $loggedIn = false;

	// defines if user just logged in
	private $isNew = false;

	// defines the provider used
	private $authProvider = "";

	/* user data */
	private $userName = "";
	private $realName = "";
	private $imageUrl = "";
	private $profileUrl = "";
	private $externalId = "";

	/* generates the auth config and returns it as an array */
	public static function getAuthLibConfig(){
		return array(
			"path" => "/auth/",
			"callback_url" => "/",
			"security_salt" => CONFIG_AUTH_SECURITYSALT,
			"Strategy" => array(
		        "Facebook" => array(
		                "app_id" => CONFIG_AUTH_FACEBOOK_ID,
		                "app_secret" => CONFIG_AUTH_FACEBOOK_SECRET
		        ),

		        "Google" => array(
		                "client_id" => CONFIG_AUTH_GOOGLE_ID,
		                "client_secret" => CONFIG_AUTH_GOOGLE_SECRET
		        ),

		        "Twitter" => array(
		                "key" => CONFIG_AUTH_TWITTER_ID,
		                "secret" => CONFIG_AUTH_TWITTER_SECRET
		        ),

		        "GitHub" => array(
	        			"client_id" => CONFIG_AUTH_GITHUB_ID,
	        			"client_secret" => CONFIG_AUTH_GITHUB_SECRET
				),

				"Bitbucket" => array(
    					"key" => CONFIG_AUTH_BITBUCKET_ID,
    					"secret" => CONFIG_AUTH_BITBUCKET_SECRET
				)
			)
		);
	}

	/* returns the users external id */
	public function getExtId(){
		return $this->authProvider."_".$this->externalId;
	}

	/* constructs the user class and expects 
		the database handle which will be set 
		to the local db variable */
	public function __construct($dbh){
		// create instance of the auth lib
		$this->authLib = new Opauth(user::getAuthLibConfig(),false);

		// set the local db handle
		$this->db = $dbh;
	
		// verify the auth cookie if it exists
		$this->verifyAuthCookie();

		// verify the callback and set the 
		// login, create the user in the 
		// database and perform required 
		// operations when the user logged in
		$this->verifyCallback();
	}

	/* returns an array with the user info */
	public function getUserInfo(){
		return array(
			"username" => $this->userName,
			"realname" => $this->realName,
			"imageurl" => $this->imageUrl,
			"profileurl" => $this->profileUrl,
			"is_admin" => $this->db->userIsAdmin($this->getExtId()),
			"externalid" => $this->getExtId(),
			"authprovider" => $this->authProvider
		);
	}

	/* returns whether the user just logged in at this call */
	public function isNewUser(){
		return $this->isNew;
	}

	/* returns whether user has administrator privileges or not */
	public function isAdmin(){
		// call db procedure and pass external id
		return $this->db->userIsAdmin($this->getExtId());
	}

	/* verifies whether a callback exists 
		and stores the login data if it 
		exists, if not it just does nothing */
	private function verifyCallback(){
		$response = null; // default value
		/* check the transport and retireve the response */
		switch($this->authLib->env['callback_transport']){
	        case 'session':
	                session_start();
	                if(array_key_exists("opauth", $_SESSION)){
	                	$response = $_SESSION['opauth'];
	                	unset($_SESSION['opauth']);
	                }
	                break;
	        case 'post':
	                $response = unserialize(base64_decode($_POST['opauth']));
	                break;
	        case 'get':
	                $response = unserialize(base64_decode($_GET['opauth']));
	                break;
		}

		/* get the response when there was no error
			and the response could be retrieved before */
		if(!is_null($response)){
			if(!array_key_exists("error",$response)){
				/* verify the response with the auth lib */
				if($this->authLib->validate(sha1(print_r($response['auth'], true)), 
					$response['timestamp'], $response['signature'], $reason)){

					// set the user data locally and set login flag
					$this->authProvider = strtolower($response["auth"]["provider"]);
					$this->externalId = $response["auth"]["uid"];
					$this->imageUrl = $response["auth"]["info"]["image"];
					$this->loggedIn = true;
					$this->isNew = true;

					// define the username with the data
					// provided by the auth provider
					if($this->authProvider=="facebook"){
						$this->userName = $response["auth"]["raw"]["username"];
						$this->realName = $response["auth"]["info"]["name"];
						$this->profileUrl = reset($response["auth"]["info"]["urls"]);
					}if($this->authProvider=="google"){
						/* google doesnt really have a username 
						and therefor we use Google's UID */
						$this->userName = $response["auth"]["uid"];
						$this->realName = $response["auth"]["info"]["name"];
						$this->profileUrl = reset($response["auth"]["info"]["urls"]);
					}if($this->authProvider=="twitter"){
						$this->userName = $response["auth"]["info"]["nickname"];
						$this->realName = $response["auth"]["info"]["name"];
						$this->profileUrl = reset($response["auth"]["info"]["urls"]);
					}if($this->authProvider=="bitbucket"){
						$this->userName = $response["auth"]["info"]["nickname"];
						$this->realName = $response["auth"]["info"]["first_name"]
										." ".$response["auth"]["info"]["last_name"];
						$this->profileUrl = "https://bitbucket.org/".$this->externalId;
					}if($this->authProvider=="github"){
						$this->userName = $response["auth"]["info"]["nickname"];
						if(array_key_exists("name", $response["auth"]["info"])){
							$this->realName = $response["auth"]["info"]["name"];
						}else{
							$this->realName = $this->userName;	
						}
						$this->profileUrl = "https://github.com/".$this->userName;
					}

					// add this user to the database or update his profile
					$db_user_extid = $this->authProvider."_".$this->externalId;
					$this->db->addUser($this->userName, $db_user_extid,$this->realName,
										"",$this->imageUrl,$this->profileUrl);

					// finally set the auth cookie
					$this->setAuthCookie();
				}
			}
		}
	}

	/* stores an encrypted cookie in the users browser 
		that contains the IP address and the external id 
		to be used for further authentication */
	public function setAuthCookie(){
		// get the cookie value or the auth key
		$authCookieVal = $this->getAuthKey();

		// finally set the cookie value and let it expire in 7 days
		setcookie(user::AUTH_COOKIE_NAME,$authCookieVal,
				(time()+(86400*7)),"/",$_SERVER["HTTP_HOST"],
				false,true);
	}

	/* checks whether an auth cookie exists or not and 
		returns true when one exists, otherwise false */
	public function hasAuthCookie(){
		$result = false;

		if(array_key_exists(user::AUTH_COOKIE_NAME, $_COOKIE)){
			$result = true;
		}

		return $result;
	}

	/* generates the auth key for the cookie and 
		the single sign on key which are basically 
		the same data */
	public function getAuthKey(){
		/* the cookie contains the external id, the remote ip address and 
			the user agent of the client. The purpose if this is to decrypt 
			later and verify whether the client is still the same or the 
			cookie value was just copied */
		$plainKeyVal = $this->authProvider."_".$this->externalId.";"
						.base64_encode($_SERVER["REMOTE_ADDR"]).";"
						.base64_encode($_SERVER["HTTP_USER_AGENT"]);

		// encrypt the plain cookie value
		$authKey = crypto::encryptText($plainKeyVal,CONFIG_AUTH_COOKIEKEY);
	
		return $authKey;
	}

	/* checks if there is an auth cookie, decrypts it and does the basic 
		integrity verification to ensure it was not touched by a mitm. */
	public function verifyAuthCookie(){
		// first check if there is a logout request in the get params
		if(array_key_exists("logout", $_GET)){
			$this->deleteAuthCookie();
		}else{
			// check if the auth cookie exists
			if(array_key_exists(user::AUTH_COOKIE_NAME, $_COOKIE)){
				// decrypt the cookie value
				$rawAuthCookie = $_COOKIE[user::AUTH_COOKIE_NAME];
				$rawAuthCookie = urldecode($rawAuthCookie);
				$rawAuthCookie = str_replace(" ", "+", $rawAuthCookie);
				
				// verify the auth key
				$verifyResult = $this->verifyAuthKey($rawAuthCookie);
				if(!$verifyResult){
					// the cookie is stolen or invalid for this
					// which might also be due to a browser update
					// along with a change in the user agent
					$this->deleteAuthCookie();
				}
			}
		}
	}

	/* deletes the auth cookie which logs out 
		the user from his current account */
	public function deleteAuthCookie(){
		// then delete the cookie, by setting 
		// its expiry date to a past time
		setcookie(user::AUTH_COOKIE_NAME,"",time()-86400,
					"/",$_SERVER["HTTP_HOST"],false,true);
	}

	/* verifies the auth key and loads the user data */
	public function verifyAuthKey($authKey){
		$result = false;

		// decrypt the auth key value
		$plainKey = crypto::decryptText($authKey,CONFIG_AUTH_COOKIEKEY);

		// check the auth key's integrity
		$keyVars = explode(";", $plainKey);
		if(base64_decode($keyVars[1])==$_SERVER["REMOTE_ADDR"]
			&&base64_decode($keyVars[2])==$_SERVER["HTTP_USER_AGENT"]){
			// integrity of the auth key is ok
			$this->loadUserData($keyVars[0]);
			$result = true;
		}

		return $result;
	}

	/* loads the user data from the database using 
		the locally stored external user id */
	private function loadUserData($uniqueExtId){
		$userData = $this->db->getUser($uniqueExtId);

		$extIdVars = explode("_", $uniqueExtId);
		$this->authProvider = $extIdVars[0];
		$this->externalId = $extIdVars[1];
		$this->realName = $userData["user_realname"];
		$this->profileUrl = $userData["user_profileurl"];
		$this->imageUrl = $userData["user_imageurl"];
		$this->loggedIn = true;		
	}

	/* returns whether user is logged in or not */
	public function isLoggedIn(){
		return $this->loggedIn;
	}
}
?>